Ed25519 support in the OpenSSL compatibility layer#10722
Conversation
|
Can one of the admins verify this patch? |
|
Hi @ordex , this is a very nice contribution. I'd like to hear more about your project and use case. In order to accept this we need some more information and a signed contributor agreement. Please email support@wolfssl.com and reference this PR. Thank you so much, David Garske, wolfSSL |
|
|
Hi @dgarske I reached out to support and I am sorting out the agreement. Regarding my project: I am working on a slim openvpn-compatible client and it is expected to perform the TLS handshake using Ed25519 keys. While this works flawlessly with OpenSSL, I hit the mentioned limitation with wolfSSL. The reason for using also wolfSSL, next to OpenSSL, is because I am planning to deploy the client on OpenWrt, which may ship with wolfSSL only. |
|
Okay to test. Thank you @ordex for those details and working with us on the contributor agreement. I see your ZD ticket 22003. |
There was a problem hiding this comment.
Pull request overview
This PR extends wolfSSL’s OpenSSL-compatibility layer to fully support Ed25519 workflows (EVP keygen, PKCS#8/SPKI serialization, X509 self-signing, and SSL_CTX loading), aligning the OpenSSL API surface with existing wolfCrypt/TLS Ed25519 capabilities.
Changes:
- Add Ed25519 support to
EVP_PKEY_keygen()and public-key DER serialization (i2d_PUBKEY/i2d_PublicKey). - Improve Ed25519 PKCS#8 decode handling and ensure decoded private keys have a derived public component.
- Add an API-level unit test exercising an application-like Ed25519 EVP/X509/TLS flow.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
wolfcrypt/src/evp.c |
Adds Ed25519 handling to EVP_PKEY key generation and caches PKCS#8 DER. |
wolfcrypt/src/evp_pk.c |
Improves Ed25519 PKCS#8 decode behavior and adds SPKI encoding for Ed25519 public keys. |
src/x509.c |
Adds Ed25519 pubkey extraction/import, Ed25519 signing handling with NULL digest, and X509 pubkey setting. |
src/pk.c |
Extends PKCS#8 encoding logic to return cached Ed25519 PKCS#8 bytes. |
tests/api.c |
Adds an end-to-end Ed25519 OpenSSL-compat EVP/X509/SSL_CTX unit test. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_EXPORT) | ||
| ctx->pkey->type != WC_EVP_PKEY_ED25519 && | ||
| #endif |
| #if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_EXPORT) | ||
| case WC_EVP_PKEY_ED25519: |
| if (wc_ed25519_make_key(&pkey->rng, ED25519_KEY_SIZE, | ||
| pkey->ed25519) == 0) { | ||
| /* Cache the PKCS#8 PrivateKeyInfo DER so the EVP/SSL paths | ||
| * (use_PrivateKey, EVP_PKEY2PKCS8) can load and serialize the | ||
| * key, mirroring the state the decode path produces. */ |
| #if defined(HAVE_ED25519) | ||
| if (key->type == WC_EVP_PKEY_ED25519) { | ||
| key->ed25519 = (ed25519_key*)XMALLOC(sizeof(ed25519_key), | ||
| x509->heap, DYNAMIC_TYPE_ED25519); | ||
| if (key->ed25519 == NULL) { |
| #if defined(HAVE_ED25519) | ||
| if (x509->pubKeyOID == ED25519k) { | ||
| ed25519 = (ed25519_key*)XMALLOC(sizeof(ed25519_key), NULL, | ||
| DYNAMIC_TYPE_ED25519); | ||
| if (ed25519 == NULL) { |
| #if defined(OPENSSL_EXTRA) && defined(HAVE_ED25519) && \ | ||
| defined(HAVE_ED25519_KEY_EXPORT) && defined(HAVE_ED25519_KEY_IMPORT) && \ | ||
| defined(WOLFSSL_CERT_GEN) && !defined(NO_CERTS) |
| tmp = p8der; | ||
| ExpectNotNull(decPriv = wolfSSL_d2i_AutoPrivateKey(NULL, &tmp, p8Sz)); | ||
|
|
||
| XFREE(p8der, HEAP_HINT, DYNAMIC_TYPE_OPENSSL); |
| XFREE(spki, HEAP_HINT, DYNAMIC_TYPE_OPENSSL); | ||
| XFREE(spki2, HEAP_HINT, DYNAMIC_TYPE_OPENSSL); |
| else if (pkey->type == WC_EVP_PKEY_ED25519) { | ||
| /* The cached DER is already a PKCS#8 PrivateKeyInfo (set when the | ||
| * key was generated or decoded), so return it as-is (same as the | ||
| * DH special case above). */ | ||
| if (keySz == NULL) | ||
| return BAD_FUNC_ARG; | ||
|
|
||
| *keySz = (word32)pkey->pkey_sz; | ||
| if (key == NULL) | ||
| return LENGTH_ONLY_E; | ||
|
|
||
| XMEMCPY(key, pkey->pkey.ptr, (size_t)pkey->pkey_sz); | ||
| return pkey->pkey_sz; | ||
| } |
dgarske
left a comment
There was a problem hiding this comment.
Skoll Code Review
Scan type: reviewOverall recommendation: REQUEST_CHANGES
Findings: 13 total — 13 posted, 0 skipped
12 finding(s) posted as inline comments (see file-level comments below)
Posted findings
- [High] Ed25519 pubkey import in CertFromX509/wolfssl_x509_make_der not gated on HAVE_ED25519_KEY_IMPORT —
src/x509.c:12522-12550 - [High] Ed25519 import paths missing HAVE_ED25519_KEY_IMPORT guard (build break) —
src/x509.c:6582, src/x509.c:12522 - [High] Ed25519 public-key decode in wolfSSL_X509_get_pubkey not gated on HAVE_ED25519_KEY_IMPORT —
src/x509.c:6582-6610 - [Medium] pkcs8_encode Ed25519 special case lacks NULL guard on pkey-pkey.ptr —
src/pk.c:7377-7392 - [Medium] Non-ASCII em-dash in comment violates wolfSSL ASCII-only convention —
src/pk.c:7380 - [Medium] Ed25519 keygen path uses KEY_EXPORT guard but calls MAKE_KEY-only wc_ed25519_make_key —
wolfcrypt/src/evp.c:3939-3958 - [Medium] wc_ed25519_make_public derive call requires HAVE_ED25519_MAKE_KEY guard —
wolfcrypt/src/evp_pk.c:286-295 - [Low] pkcs8_encode Ed25519 branch assumes cached DER is a private PKCS#8 without validating —
src/pk.c:7377-7392 - [Low] Non-ASCII em-dash in comment violates ASCII-only convention —
src/pk.c:7380 - [Low] Use CTC_ED25519 instead of ED25519k for signature-type return value —
src/x509.c:12264-12269 - [Low] Doxygen comment detached from wolfSSL_i2d_PublicKey by inserted helper —
wolfcrypt/src/evp_pk.c:2402-2457 - [Low] i2d_PublicKey for Ed25519 emits SubjectPublicKeyInfo rather than the raw key —
wolfcrypt/src/evp_pk.c:2459-2488 - [Low] Redundant pubKeySet assignment and ignored make_public failure —
wolfcrypt/src/evp_pk.c:290-295
Review generated by Skoll
julek-wolfssl
left a comment
There was a problem hiding this comment.
This is great work. I would like to see the following tests included too:
- Raw-key paths, e.g.
EVP_PKEY_new_raw_private_key()/EVP_PKEY_new_raw_public_key(), since those may populatepkey->pkey.ptrdifferently than keygen/decode. - Validation that a PKCS#8-decoded Ed25519 private key can re-export the same public key / SPKI, not just that
d2i_AutoPrivateKey()returns non-NULL. - Negative coverage for public-only Ed25519 keys: PKCS#8 private export should fail cleanly, not emit raw public bytes or cached non-PKCS#8 data.
- Certificate verification after
X509_sign(..., NULL), to prove the generated Ed25519 signature/OID/public-key encoding is actually usable.
|
Hi @ordex , your contributor agreement has been approved and is now on file. We posted some review feedback, so take a look at those. Looking forward to your next push. |
|
I will be squashing the various requested changes into the original commits. I think this is fine, right? |
Yes that is fine. Thank you |
wolfCrypt has full Ed25519 (keygen, sign/verify, ASN.1 import/export) and the TLS 1.3 stack already authenticates with Ed25519 certificates, but the OpenSSL-compatibility surface was missing the dispatch for several common operations, so an application driving Ed25519 purely through the OpenSSL API (EVP_PKEY_keygen, i2d_PUBKEY, X509_sign of an in-memory self-signed cert, X509_verify, then loading it into an SSL_CTX) could not use it. Fill those gaps, each mirroring the adjacent RSA/ECC/X25519 case: - EVP_PKEY_keygen (wolfcrypt/src/evp.c): generate an Ed25519 key and cache the PKCS#8 PrivateKeyInfo DER, like the EC/RSA cases; if the DER cannot be cached the freshly generated key is freed so a failed keygen never leaves a partial key behind. - wolfSSL_i2d_PublicKey / i2d_PUBKEY (wolfcrypt/src/evp_pk.c): encode an Ed25519 SubjectPublicKeyInfo. i2d_PublicKey intentionally emits the full SPKI here (rather than the bare key) because i2d_PUBKEY aliases it and the PEM_write_PUBKEY / d2i_PUBKEY round-trip relies on it. - pkcs8_encode (src/pk.c): return the already-PKCS#8 cached DER for Ed25519 (as the DH case does) after checking it is present, so i2d_PKCS8_PRIV_KEY_INFO works and a public-only key (e.g. one obtained from X509_get_pubkey, which caches only the raw public key) is rejected rather than emitted as a bogus private key. - d2iTryEd25519Key (wolfcrypt/src/evp_pk.c): derive the public key after decoding a PKCS#8 private key (v1 carries only the seed) so the decoded EVP_PKEY is complete. - d2i_AutoPrivateKey (wolfcrypt/src/evp_pk.c): detect Ed25519 by its algorithm id and decode the full PKCS#8 (its inner key is an OCTET STRING, which the RSA/ECC sequence-counting heuristic cannot classify). - X509_sign / X509_resign_cert / sigTypeFromPKEY / wolfssl_x509_make_der (src/x509.c): sign a certificate with an Ed25519 key (NULL digest), resolving the signature type as CTC_ED25519 and building the SubjectPublicKey from an ed25519_key. - X509_verify (src/x509.c): verify a certificate's signature with an Ed25519 EVP_PKEY, exporting the raw public key for the check (the cached DER is a PKCS#8 private blob, not the public key the verify path wants). - X509_set_pubkey / X509_get_pubkey (src/x509.c): set and retrieve an Ed25519 public key, keeping the X.509 public-key buffer as the raw key bytes to match how DecodeCert/StoreKey store it. Ed25519 object lifetime goes through the existing wolfSSL_ED25519_new / wolfSSL_ED25519_free helpers. Their guards (wolfssl/openssl/ed25519.h, src/pk.c) and the ssl.c include of openssl/ed25519.h are widened to also cover OPENSSL_EXTRA_X509_SMALL, since the X509 paths above build in that configuration too. The individual operations are gated on HAVE_ED25519_KEY_IMPORT / HAVE_ED25519_KEY_EXPORT / HAVE_ED25519_MAKE_KEY so a build that disables one of those sub-features still compiles. tests/api.c gains test_wolfSSL_EVP_PKEY_ED25519_openssl, which exercises the whole sequence: keygen, i2d_PUBKEY, the raw-key constructors (EVP_PKEY_new_raw_public_key / EVP_PKEY_new_raw_private_key), the PKCS#8 round-trip (EVP_PKEY2PKCS8, i2d_PKCS8_PKEY, d2i_AutoPrivateKey) including a check that the decoded key re-exports the same SPKI, a negative check that PKCS#8 export of a public-only key fails cleanly, a self-signed X509_sign with a NULL digest followed by X509_verify of the result, an X509_get_pubkey round-trip, and loading the key+cert into an SSL_CTX. Requires --enable-ed25519 --enable-certgen (key import/export and key generation are on by default with --enable-ed25519). Signed-off-by: Antonio Quartulli <antonio@mandelbit.com>
julek-wolfssl
left a comment
There was a problem hiding this comment.
@ordex Please address the one unresolved issue remaining from David. I commented what is still needed.
Thanks for pointing that out! Will look at it ASAP. |
Description
wolfCrypt has full Ed25519 (keygen, sign/verify, ASN.1 import/export) and the TLS 1.3 stack already authenticates with Ed25519 certificates, but the OpenSSL-compatibility surface was missing the dispatch for several common operations, so an application driving Ed25519 purely through the OpenSSL API (EVP_PKEY_keygen, i2d_PUBKEY, X509_sign of an in-memory self-signed cert, then loading it into an SSL_CTX) could not use it.
This PR adds the required glue code to have full OpenSSL API support for Ed25591 (by mirroring the existing RSA/ECC/X25519 code).
Note: requires --enable-ed25519 --enable-certgen
Testing
The commit comes with the relevant unit test code.
Moreover, this code is used by an experimental TLS-based VPN client that uses Ed25591 keys/certs.